﻿using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Abp.Extensions;
using Abp.Localization;
using Abp.Runtime.Security;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging;
using MyCompanyName.MyProject.ETag;

namespace MyCompanyName.MyProject.Middleware
{
    public class WebApiEtagMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILoggerFactory _loggerFactory;
        private readonly IETagStoreManger _eTagStoreManger;
        private readonly ILanguageManager _languageManager;
        private readonly ILogger _logger;
        private readonly ETagEndPointOptions _etagEndPointOptions;

        public WebApiEtagMiddleware([NotNull] RequestDelegate next,
            [NotNull] ILoggerFactory loggerFactory,
            [NotNull] IOptions<ETagEndPointOptions> etagEndPointOptions,
            [NotNull] IETagStoreManger eTagStoreManger,
            [NotNull] ILanguageManager languageManager)
        {
            if (etagEndPointOptions == null) throw new ArgumentNullException(nameof(etagEndPointOptions));
            _next = next ?? throw new ArgumentNullException(nameof(next));
            _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
            _eTagStoreManger = eTagStoreManger ?? throw new ArgumentNullException(nameof(eTagStoreManger));
            _languageManager = languageManager ?? throw new ArgumentNullException(nameof(languageManager));
            _etagEndPointOptions = etagEndPointOptions?.Value;
            _logger = (ILogger)loggerFactory.CreateLogger<WebApiEtagMiddleware>();
        }

        public async Task Invoke(HttpContext httpContext)
        {
            if (!ConditionalGetOrHeadIsValid(httpContext))
            {
                await _next.Invoke(httpContext);
                return;
            }

            var tenantId = httpContext.User.Identity.GetTenantId();
            var userId = httpContext.User.Identity.GetUserId();

            var culture = _languageManager.CurrentLanguage.Name;

            ETagCacheKey eTagCacheKey = new ETagCacheKey(httpContext.Request.Path.Value.ToLower(), tenantId, userId, culture);

            var ETagCacheValue = await _eTagStoreManger.GetETagAsync(eTagCacheKey, async () =>
            {
                EtagCacheValue etagCacheValue = new EtagCacheValue();

                var originalBodyStream = httpContext.Response.Body;
                string eTag;
                using (var responseBody = new MemoryStream())
                {
                    httpContext.Response.Body = responseBody;

                    await _next.Invoke(httpContext);

                    var bodyContent = await GetResponse(httpContext.Response);

                    eTag = $"{tenantId}:{userId}-{bodyContent}".ToMd5();
                    etagCacheValue.ETag = eTag;
                    etagCacheValue.BodyText = bodyContent;

                    httpContext.Response.Headers.Add("Cache-Control", "pulic");
                    httpContext.Response.Headers.Add("ETag", eTag);
                    await responseBody.CopyToAsync(originalBodyStream);
                }
                return etagCacheValue;
            });

            // 先实现 根据 httpContext的返回值 检测Tag，减少网络传输，并不会减少服务器执行
            string str = httpContext.Request.Headers["If-None-Match"].ToString().Trim();
            if (string.Equals(str, ETagCacheValue.ETag, StringComparison.OrdinalIgnoreCase))
            {
                httpContext.Response.Headers.Clear();
                httpContext.Response.StatusCode = 304;
                return;
            }

            if (!httpContext.Response.HasStarted)
            {
                httpContext.Response.ContentType = "application/json; charset=utf-8";
                httpContext.Response.Headers.Add("Cache-Control", "pulic");
                httpContext.Response.Headers.Add("ETag", ETagCacheValue.ETag);
                await httpContext.Response.WriteAsync(ETagCacheValue.BodyText);
            }
        }

        private async Task<string> GetResponse(HttpResponse response)
        {
            response.Body.Seek(0, SeekOrigin.Begin);
            var text = await new StreamReader(response.Body).ReadToEndAsync();
            response.Body.Seek(0, SeekOrigin.Begin);
            return $"{text}";
        }

        private bool ConditionalGetOrHeadIsValid(HttpContext httpContext)
        {
            //this._logger.LogInformation("Checking for conditional GET/HEAD.", Array.Empty<object>());
            if (!string.Equals(httpContext.Request.Method, HttpMethod.Get.ToString(), StringComparison.OrdinalIgnoreCase))
            {
                //this._logger.LogInformation("Not valid - method isn't GET or HEAD.", Array.Empty<object>());
                return false;
            }

            if (!_etagEndPointOptions.EndPoints.Any(e =>
                string.Equals(e, httpContext.Request.Path.Value, StringComparison.OrdinalIgnoreCase)))
            {
                return false;
            }

            return true;
        }
    }
}